﻿using Fluid;
using Fluid.Ast;
using Parlot.Fluent;
using ZYing.LiquidThemeEngine.Ast;
using ZYing.LiquidThemeEngine.Tags;
using static Parlot.Fluent.Parsers;

namespace ZYing.LiquidThemeEngine
{
    /// <summary>
    ///     Liquid 语法解析器
    /// </summary>
    public class LiquidParser : FluidParser
    {
        public LiquidParser()
        {
            AddLayoutTags();
            AddPaginate();
            AddRender();
            AddHtmlBlocks();
            AddDataTags();
            AddSchemaTag();
            AddSectionTag();
        }

        private void AddPaginate()
        {
            RegisterParserBlock(
                "paginate",
                LogicalExpression.AndSkip(Terms.Text("by")).And(Terms.Integer()),
                PaginatingTags.PaginateAsync
            );
        }
        private void AddSectionTag()
        {
            RegisteredTags["section"] = FilterExpression.AndSkip(TagEnd).Then<Statement>(x => new SectionStatement(x));
        }

        private void AddLayoutTags()
        {
            RegisterExpressionTag("layout", LayoutTags.LayoutAsync);
            RegisterEmptyTag("render_body", LayoutTags.RenderBodyAsync);
            RegisterEmptyTag("render_index", LayoutTags.RenderIndexAsync);
            AddSlot();
        }

        private void AddHtmlBlocks()
        {
            RegisterEmptyBlock("style", HtmlTags.Style);
        }
        private void AddDataTags()
        {
            RegisterEmptyTag("var_dump", DataTags.VarDumpAsync);
        }
        private void AddSlot()
        {
            RegisterIdentifierTag("slot", LayoutTags.Solt);
            RegisterIdentifierBlock("template", LayoutTags.Template);
        }
        private void AddSchemaTag()
        {
            RegisteredTags["schema"] = TagEnd
                        .SkipAnd(AnyCharBefore(CreateTag("endschema")))
                        .AndSkip(CreateTag("endschema").ElseError($"'{{% endschema %}}' was expected"))
                        .Then<Statement>(x => new SchemaStatement(x))
                        .ElseError("Invalid 'schema' tag");
        }
        private static AssignStatement AssignStatement((string identifier, Expression value) args)
        {
            return new AssignStatement(args.identifier, args.value);
        }

        private void AddRender()
        {
            RegisteredTags["render"] = Render("render");
        }

        private Parser<Statement> Render(string name)
        {
            return OneOf(
                    Primary
                        .AndSkip(Comma)
                        .And(
                            Separated(
                                Comma,
                                Identifier.AndSkip(Colon).And(Primary).Then(AssignStatement)
                            )
                        )
                        .Then(
                            x =>
                                new RenderStatement(x.Item1, null, x.Item2)
                        ),
                    Primary
                        .AndSkip(Terms.Text("with"))
                        .And(Primary)
                        .Then(
                            x =>
                                new RenderStatement(x.Item1, x.Item2, null)
                        ),
                    Primary
                        .Then(
                            x => new RenderStatement(x, null, null)
                        )
                ).AndSkip(TagEnd)
                .Then<Statement>(x => x)
                .ElseError($"Invalid '{name}' tag");
        }
    }
}